package com.example.javafx.bilibilidownload.service;


import com.example.javafx.bilibilidownload.constant.BiliVideoEnum;
import com.example.javafx.bilibilidownload.constant.UserAgentConstant;
import com.example.javafx.bilibilidownload.constant.VideoConstant;
import com.example.javafx.bilibilidownload.pojo.BiliErrorPojo;
import com.example.javafx.bilibilidownload.pojo.BiliFavoriteSimplePojo;
import com.example.javafx.bilibilidownload.pojo.BiliFocusPojo;
import com.example.javafx.bilibilidownload.pojo.BiliFuckWbiInfo;
import com.example.javafx.bilibilidownload.pojo.BiliUpPojo;
import com.example.javafx.bilibilidownload.pojo.BiliVideoPojo;
import com.example.javafx.bilibilidownload.pojo.CommandResult;
import com.example.javafx.bilibilidownload.util.BiliFileUtil;
import com.example.javafx.bilibilidownload.util.BiliUtil;
import com.example.javafx.bilibilidownload.util.BiliWbiUtil;
import com.example.javafx.bilibilidownload.util.CacheUtil;

import com.example.javafx.bilibilidownload.util.CommandUtils;
import com.example.javafx.bilibilidownload.util.DateUtil;
import com.example.javafx.bilibilidownload.util.HtmlUtil;
import com.example.javafx.bilibilidownload.util.JsoupUtils;
import com.example.javafx.bilibilidownload.util.OpetateFileUtil;
import com.example.javafx.bilibilidownload.util.VideoCommandUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jdk.jfr.Description;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFileFilter;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.example.javafx.bilibilidownload.constant.BilibiliApiConstant.*;
import static com.example.javafx.bilibilidownload.util.BiliFileUtil.FS;
import static com.example.javafx.bilibilidownload.util.BiliFileUtil.biliDefaultPageSize;
import static com.example.javafx.bilibilidownload.util.BiliFileUtil.biliMainDir;
import static com.example.javafx.bilibilidownload.util.BiliFileUtil.biliUpVideoDir;
import static com.example.javafx.bilibilidownload.util.BiliFileUtil.checkFileExist;
import static com.example.javafx.bilibilidownload.util.BiliFileUtil.getAbsoluteFile;
import static com.example.javafx.bilibilidownload.util.BiliFileUtil.getUpDownloadDir;
import static com.example.javafx.bilibilidownload.util.UrlUtil.getTreeMap;


public class BiliServiceImpl {

    private static final Logger log = LogManager.getLogger(BiliServiceImpl.class);


    public static final int MAX_LENGTH = 60 * 60;
    public static Map header = new HashMap();

    private static final int DEFAULT_PAGESIZE = 30;

    private static ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    //视频下的分辨率不够，要及时更新cookie
    static {
        String cookie = "buvid3=525ECC5F-52A6-5F1D-9315-714CD7AB62D653908infoc; b_nut=1726734253; _uuid=77BEF58C-71010E-84E1-F51010-412A10E3D172D35832infoc; header_theme_version=CLOSE; enable_web_push=DISABLE; SESSDATA=d2e0102a%2C1742350776%2Cd1802%2A91CjD1VTQmgZjvrueSZHgNb1UOgXJy1sCl7ShUrKuSkd48uGZDQCeZgDErzYGyAEE3aEkSVnVsSTZ2WF9LY25uQll5dHhwWXJJR0pSSzM0V21GYi02eUl0c0VJYnB6eGhrT3dKV09lVmhFcThDT29tY3VYaDlVZEZKMjRUZ2hoVlNpZ0psY1QwVGlBIIEC; bili_jct=e2dc00ce1ec955a9f2dd755b0dc177ce; DedeUserID=34156135; DedeUserID__ckMd5=a4148ee7675a3bcb; sid=8cdbrznu; rpdid=|(JY)JJm)~ll0J'u~kY~m))uJ; buvid_fp_plain=undefined; buvid4=00E41413-AC1B-5A4E-04FE-9D1B560A4EA053908-024091908-E6sQB397Mycy0IeAh5%2FdDA%3D%3D; LIVE_BUVID=AUTO9917286157694778; CURRENT_QUALITY=120; match_float_version=ENABLE; share_source_origin=QQ; bsource=search_google; fingerprint=11dea2a76e12bc0d13f9d9082a4aee3b; buvid_fp=0c44a9779e09217fffe8176353323485; enable_feed_channel=DISABLE; bili_ticket=eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3Mzc4NjM5MzksImlhdCI6MTczNzYwNDY3OSwicGx0IjotMX0.5mZXYM_QAtqpanv0ZQ3ETxo4tjFmxBK1lnjWCHa5O2s; bili_ticket_expires=1737863879; CURRENT_FNVAL=4048; b_lsid=4621034FB_194960C1500; bp_t_offset_34156135=1025834740873166848; home_feed_column=4; browser_resolution=1101-591";
        header.put("Content-Type", "application/json;charset=UTF-8");
        header.put("cookie", cookie);
        header.put("origin", "https://space.bilibili.com");
        header.put("referer", "https://www.bilibili.com");
        header.put("sec-fetch-mode", "cors");
        header.put("sec-fetch-dest", "empty");
        header.put("sec-fetch-site", "same-site");
        header.put("user-agent", UserAgentConstant.getIndex());
        header.put("'accept'", "*/*");
        header.put("accept-encoding", "'identity'");
    }

    /**
     * @Description: 获取我的所有收藏夹
     * @Author: bangbang23333
     * @Date: 2024/8/8 10:39
     */
    public List<BiliFavoriteSimplePojo> refreshMyFavorite() throws Exception {
        List<BiliFavoriteSimplePojo> biliFavoriteSimplePojos = new ArrayList<>();
        String resultInfo = JsoupUtils.getStringDoc(getMyFavoriteList, header);
        JsonNode myfavoriteList = objectMapper.readTree(resultInfo);
        for (JsonNode tag : myfavoriteList.get("data")) {
            for (JsonNode node : tag.get("mediaListResponse").get("list")) {
                log.info("收藏夹名字是:{}, id是:{}", node.get("title").asText(), node.get("id").asText());
                BiliFavoriteSimplePojo biliFavoriteSimplePojo = new BiliFavoriteSimplePojo();
                biliFavoriteSimplePojo.setTitle(node.get("title").asText());
                biliFavoriteSimplePojo.setId(node.get("id").asText());
                biliFavoriteSimplePojo.setCount(node.get("media_count").asInt());
                biliFavoriteSimplePojos.add(biliFavoriteSimplePojo);
                // 写入文件保存
                String fileName = BiliFileUtil.getFavoriteFile(biliFavoriteSimplePojo.getTitle() + ".txt");
                // 如果文件已存在就不写入
                BiliFileUtil.isNotExistAndNeedWriteDown(fileName, biliFavoriteSimplePojo);
            }
        }
        return biliFavoriteSimplePojos;
    }

    public List<BiliFavoriteSimplePojo> getMyFavorite() {
        List<BiliFavoriteSimplePojo> biliFavoriteSimplePojos = new ArrayList<>();
        String path = BiliFileUtil.getFavoritedir();
        File[] listFiles = getAbsoluteFile(path).listFiles((FileFilter) FileFileFilter.INSTANCE);
        if (null == listFiles || listFiles.length == 0) {
            return biliFavoriteSimplePojos;
        }
        Arrays.stream(listFiles).filter(index -> !index.getName().contains("Error")).forEach(index -> {
            BiliFavoriteSimplePojo biliFavoriteSimplePojo = BiliFileUtil.readInfo(index, BiliFavoriteSimplePojo.class);
            biliFavoriteSimplePojos.add(biliFavoriteSimplePojo);
        });
        return biliFavoriteSimplePojos;
    }

    /**
     * @Description: 根据收藏夹id获取收藏夹下的视频，分页查询
     * @Author: bangbang23333
     * @Date: 2024/8/8 10:49
     */
    public List<BiliVideoPojo> refreshMyFavoriteDetail(BiliFavoriteSimplePojo biliFavoriteSimplePojo) {
        List<BiliVideoPojo> biliVideoPojos = new ArrayList<>();
        int pageNum = biliFavoriteSimplePojo.getCount() / biliDefaultPageSize + 1;
        for (int i = 1; i <= pageNum + 1; i++) {
            String realUrl = String.format(getMyFavoriteDetailList, biliFavoriteSimplePojo.getId(), i, biliDefaultPageSize);
            String resultInfo = JsoupUtils.getStringDoc(realUrl, header);
            JsonNode myFavoriteDetailList = null;
            try {
                myFavoriteDetailList = objectMapper.readTree(resultInfo);
            } catch (JsonProcessingException e) {
                log.error("获取我的收藏视频详情失败, 收藏id是:{}, 收藏名字是:{}", biliFavoriteSimplePojo.getId(), biliFavoriteSimplePojo.getTitle(), e);
            }
            for (JsonNode node : myFavoriteDetailList.get("data").get("medias")) {
                log.info("收藏夹名字是:{}, 发现其中视频bvid是:{}, 名字是:{}", biliFavoriteSimplePojo.getTitle(), node.get("bvid").asText(), node.get("title").asText());
                // 已失效视频就跳过
                if (node.get("title").equals("已失效视频")) {
                    log.info("收藏夹名字是:{}, 发现其中视频bvid是:{}, 名字是:{}, 该视频已失效", biliFavoriteSimplePojo.getTitle(), node.get("bvid").asText(), node.get("title").asText());
                    continue;
                }
                BiliVideoPojo biliVideoPojo = new BiliVideoPojo();
                biliVideoPojo.setBvid(node.get("bvid").asText());
                biliVideoPojo.setTitle(node.get("title").asText());
                biliVideoPojo.setCreated(node.get("ctime").asText());
                biliVideoPojo.setAuthor(node.get("upper").get("name").asText());
                biliVideoPojo.setFavoriteTime(node.get("fav_time").asText());
                biliVideoPojo.setFavoriteId(biliFavoriteSimplePojo.getId());
                biliVideoPojo.setFavoriteName(biliFavoriteSimplePojo.getTitle());
                // 如果视频数量大于1，则说明是多p视频,多p视频不记录
                if (node.get("page").asInt() > 1) {
                    biliVideoPojo.setCollectionName(node.get("title").asText());
                    // 解析多p视频，并放到收藏对应目录下，写入文件中
                    refreshPageList("", biliVideoPojo.getBvid(), biliVideoPojo.getCollectionName(), biliVideoPojo.getFavoriteName(), BiliVideoEnum.Favorite);
                }
                biliVideoPojos.add(biliVideoPojo);
                // 写入文件保存
                String fileName = BiliFileUtil.getFavoriteVideoFile(biliVideoPojo.getFavoriteName(), biliVideoPojo.getTitle() + ".txt");
                // 如果文件已存在就不写入
                BiliFileUtil.isNotExistAndNeedWriteDown(fileName, biliVideoPojo);
            }
        }
        return biliVideoPojos;
    }

    public List<BiliVideoPojo> getMyFavoriteDetail(BiliFavoriteSimplePojo biliFavoriteSimplePojo) {
        List<BiliVideoPojo> biliVideoPojos = new ArrayList<>();
        String path = BiliFileUtil.getFavoriteFileDir(biliFavoriteSimplePojo.getTitle());
        File[] listFiles = getAbsoluteFile(path).listFiles((FileFilter) FileFileFilter.INSTANCE);
        if (null == listFiles || listFiles.length == 0) {
            return biliVideoPojos;
        }
        Arrays.stream(listFiles).forEach(index -> {
            BiliVideoPojo biliVideoPojo = BiliFileUtil.readInfo(index, BiliVideoPojo.class);
            biliVideoPojos.add(biliVideoPojo);
        });
        biliVideoPojos.sort(Comparator.comparing(BiliVideoPojo::getFavoriteTime).reversed());
        return biliVideoPojos;
    }

    /**
     * @Description: 获取收藏夹中集合视频列表
     * @Author: bangbang23333
     * @Date: 2024/9/29 16:11
     */
    public List<BiliVideoPojo> getMyFavoriteCollectionDetail(BiliVideoPojo biliVideoPojo) {
        List<BiliVideoPojo> biliVideoPojos = new ArrayList<>();
        String path = BiliFileUtil.getFavoriteFileDir(biliVideoPojo.getFavoriteName() + FS + biliVideoPojo.getCollectionName());
        File[] listFiles = getAbsoluteFile(path).listFiles((FileFilter) FileFileFilter.INSTANCE);
        if (null == listFiles || listFiles.length == 0) {
            return biliVideoPojos;
        }
        Arrays.stream(listFiles).forEach(index -> {
            BiliVideoPojo indexVideoPojo = BiliFileUtil.readInfo(index, BiliVideoPojo.class);
            biliVideoPojos.add(indexVideoPojo);
        });
        return biliVideoPojos;
    }

    /**
     * @Description:通过收藏夹的名字，拿到里面的所有的up信息
     * @Param: [bookMarkName]
     * @return: java.util.List<java.util.Map < java.lang.String, java.lang.String>>
     * @Author: Mr.zengbang
     * @Date: 2020/12/13
     */
    public List<BiliFocusPojo> refreshMyfocus(int userId) throws Exception {
        List<BiliFocusPojo> biliFocusPojos = new ArrayList<>();
        String resultInfo = JsoupUtils.getStringDoc(getMyFocusList, header);
        resultInfo = resultInfo.replace("__jp3(", "").replace(")", "");
        JsonNode myFocus = objectMapper.readTree(resultInfo);
        for (JsonNode tag : myFocus.get("data")) {
            BiliFocusPojo biliFocusPojo = new BiliFocusPojo();
            biliFocusPojo.setFocusId(tag.get("tagid").asText());
            biliFocusPojo.setCount(tag.get("count").asInt());
            biliFocusPojo.setFocusName(tag.get("name").asText());
            biliFocusPojo.setTips(tag.get("tip").asText());
            biliFocusPojo.setUserId(userId);
            log.info("收藏夹名字：{}, 收藏夹id:{}, 收藏夹up数:{}", biliFocusPojo.getFocusName(), biliFocusPojo.getFocusId(), biliFocusPojo.getCount());
            biliFocusPojos.add(biliFocusPojo);
            String fileName = BiliFileUtil.getFocusFile(biliFocusPojo.getFocusName());
            BiliFileUtil.isNotExistAndNeedWriteDown(fileName, biliFocusPojo);
        }
        return biliFocusPojos;
    }

    public List<BiliFocusPojo> getMyfocus() {
        List<BiliFocusPojo> biliFocusPojos = new ArrayList<>();
        String path = BiliFileUtil.getFocusDir();
        File[] listFiles = getAbsoluteFile(path).listFiles((FileFilter) FileFileFilter.INSTANCE);
        if (null == listFiles || listFiles.length == 0) {
            return biliFocusPojos;
        }
        Arrays.stream(listFiles).forEach(index -> {
            BiliFocusPojo biliFocusPojo = BiliFileUtil.readInfo(index, BiliFocusPojo.class);
            biliFocusPojos.add(biliFocusPojo);
        });
        return biliFocusPojos;
    }

    /**
     * @Description:通过tagId和userId查询所有的up名单
     * @Param: [userId, objectMapper, tagId, count, upList]
     * @return: java.util.List<java.util.Map < java.lang.String, java.lang.String>>
     * @Author: Mr.zengbang
     * @Date: 2022/4/30
     */
    public List<BiliUpPojo> refreshFocusUpList(BiliFocusPojo biliFocusPojo) {
        List<BiliUpPojo> upList = new ArrayList<>();
        for (int i = 1; i < (biliFocusPojo.getCount() / 50 + 2); i++) {
            try {
                String realUpListUrlModel = String.format(getUpListByTagId, biliFocusPojo.getUserId(), biliFocusPojo.getFocusId(), i);
                String upInfo = JsoupUtils.getStringDoc(realUpListUrlModel, header);
                upInfo = upInfo.replace("__jp5(", "").replace(")", "");
                JsonNode upJsonNode = objectMapper.readTree(upInfo);
                for (JsonNode tag : upJsonNode.get("data")) {
                    BiliUpPojo biliUpPojo = new BiliUpPojo();
                    biliUpPojo.setUpId(tag.get("mid").asText());
                    biliUpPojo.setUpName(tag.get("uname").asText());
                    biliUpPojo.setFocusName(biliFocusPojo.getFocusName());
                    biliUpPojo.setUserId(biliFocusPojo.getUserId());
                    biliUpPojo.setFocusId(biliFocusPojo.getFocusId());
                    log.info("关注列表获取up信息: 分组名:{},up名字:{},upId:{}", biliFocusPojo.getFocusName(), biliUpPojo.getUpName(), biliUpPojo.getUpId());
                    upList.add(biliUpPojo);
                    // 如果之前已经读取过了该视频信息就不读取了
                    String fileName = BiliFileUtil.getFocusUpFile(biliFocusPojo.getFocusName(), biliUpPojo.getUpName());
                    BiliFileUtil.isNotExistAndNeedWriteDown(fileName, biliUpPojo);
                }
            } catch (Exception e) {
                log.error("关注列表获取up信息失败! userId:{}, 分组名:{}", biliFocusPojo.getUserId(), biliFocusPojo.getFocusName());
            }
        }
        log.info("关注列表获取up信息完成: 关注分组名:{} ,其下关注up的数量是:{}", biliFocusPojo.getFocusName(), upList.size());
        return upList;
    }


    public List<BiliVideoPojo> getDownloadVideoInFavoriteById(String favoriteId) {
        return getDownloadVideoInFavorite(favoriteId, null);
    }

    public List<BiliVideoPojo> getDownloadVideoInFavoriteByName(String favoriteName) {
        return getDownloadVideoInFavorite(null, favoriteName);
    }

    public List<BiliVideoPojo> getDownloadVideoInFavorite() {
        return getDownloadVideoInFavorite(null, null);
    }

    public List<BiliVideoPojo> getDownloadVideoInFavorite(String favoriteId, String favoriteName) {
        List<BiliVideoPojo> videoList = new ArrayList<>();
        String path = BiliFileUtil.getFavoritedir();
        List<String> excludeStr = new ArrayList<>();
        excludeStr.add("favoriteErrorList");
        excludeStr.add(favoriteName);
        List<File> files = BiliFileUtil.getFileListByPath(path, excludeStr);
        files.forEach(index -> {
            BiliVideoPojo biliVideoPojo = BiliFileUtil.readInfo(index, BiliVideoPojo.class);
            if (StringUtils.isEmpty(favoriteId) || biliVideoPojo.getFavoriteId().equals(favoriteId)) {
                videoList.add(biliVideoPojo);
            }
        });
        return videoList;
    }

    public List<BiliUpPojo> getFocusUpList(String focusName) {
        List<BiliUpPojo> focusUpList = new ArrayList<>();
        String path = BiliFileUtil.getFocusDir() + FS + focusName;
        List<File> files = BiliFileUtil.getFileListByPath(path);
        files.parallelStream().forEach(index -> {
            BiliUpPojo upPojo = BiliFileUtil.readInfo(index, BiliUpPojo.class);
            if (focusName.isEmpty() || focusName.equals(upPojo.getFocusName())) {
                log.info("获取关注列表up信息: 分组名:{},up名字:{},upId:{}", upPojo.getFocusName(), upPojo.getUpName(), upPojo.getUpId());
                focusUpList.add(upPojo);
            }
        });
        // 按照总文件大小排序
        focusUpList.sort(Comparator.comparing((BiliUpPojo up) -> BiliFileUtil.getDirectoryLongSize(getUpDownloadDir(up.getUpName()))).reversed());
        return focusUpList;
    }

    public List<BiliVideoPojo> getSingleDownloadVideo(String bvid, String collectionName) {
        List<BiliVideoPojo> videoList = new ArrayList<>();
        String path = BiliFileUtil.getSingledir();
        List<File> files = BiliFileUtil.getFileListByPath(path);
        files.forEach(index -> {
            BiliVideoPojo biliVideoPojo = BiliFileUtil.readInfo(index, BiliVideoPojo.class);
            if (null == biliVideoPojo) {

            } else if (StringUtils.isNotEmpty(collectionName) && biliVideoPojo.getCollectionName().equals(collectionName)) {
                videoList.add(biliVideoPojo);
            } else if (StringUtils.isNotEmpty(bvid) && biliVideoPojo.getBvid().equals(bvid)) {
                videoList.add(biliVideoPojo);
            }
        });
        return videoList;
    }

    /**
     * @Description:得到up的全部视频信息
     * @Param: [userId]
     * @return: java.util.List<com.example.pojo.BilibiliVideo>
     * @Author: Mr.zengbang
     * @Date: 2022/4/30
     */
    public List<BiliVideoPojo> getUPAllVideoList(String upName, int minPlay, String beginTime, List<String> keywordFiltering) {
        List<BiliVideoPojo> videoList = new ArrayList<>();
        String path = biliMainDir + File.separator + upName + File.separator + biliUpVideoDir;
        List<File> files = BiliFileUtil.getFileListByPath(path);
        // 过滤掉小于指定播放量的视频
        //            // 过滤掉小于指定创建时间的视频
        files.stream().<BiliVideoPojo>map(index -> BiliFileUtil.readInfo(index, BiliVideoPojo.class))
                .filter(Objects::nonNull)
                .filter(biliVideoPojo -> minPlay <= 0 || biliVideoPojo.getPlay() >= minPlay)
                .filter(biliVideoPojo -> !StringUtils.isNotEmpty(beginTime) || Long.valueOf(biliVideoPojo.getCreated()) * 1000 >= DateUtil.getTimestamp(beginTime))
                .parallel()
                .forEach(biliVideoPojo -> {
                    boolean match =
                            keywordFiltering.stream().anyMatch(f -> StringUtils.containsIgnoreCase(biliVideoPojo.getTitle(), f));
                    if (keywordFiltering.isEmpty() || match) {
                        videoList.add(biliVideoPojo);
                    } else {
                        log.info("视频不包含任何关键字, 直接忽略, 视频名字是:{}", biliVideoPojo.getTitle());
                    }
                });
        // 根据创建时间排序
        videoList.sort(Comparator.comparing(BiliVideoPojo::getCreated).reversed());
        // 排除掉不能下载的视频
        return videoList.stream().filter(v -> null != v.getCanDownload() && v.getCanDownload()).collect(Collectors.toList());
    }

    /**
     * @Description:得到up的全部视频信息
     * @Param: [userId]
     * @return: java.util.List<com.example.pojo.BilibiliVideo>
     * @Author: Mr.zengbang
     * @Date: 2022/4/30
     */
    public List<BiliVideoPojo> refreshUPAllVideoList(String upId, String upName) {
        List<BiliVideoPojo> videoList = new ArrayList<>();
        BiliFuckWbiInfo loginInfo = getLoginInfo();
        String realSpaceUrl = String.format(UpPersonDetail, upId, DEFAULT_PAGESIZE, 1);
        TreeMap<String, Object> paramTreeMap = getTreeMap(realSpaceUrl);
        String wbiParamString = BiliWbiUtil.getWbiParamString(paramTreeMap, loginInfo.getImg_key(), loginInfo.getSub_key());
        // 发送请求获取数据
        JsonNode doc = JsoupUtils.getJsonNodeDoc(UpPersonDetailWithoutParam + wbiParamString, header);
        // 把数据写入到文件中
        Boolean detailInfo = BiliFileUtil.writeInfo(BiliFileUtil.getUpSpaceFile(upName), doc);
        // 写入失败就返回null
        if (!detailInfo) return videoList;
        // 获取up的总页数
        String size = doc.get("data").get("page").get("ps").asText();
        String count = doc.get("data").get("page").get("count").asText();
        int pages = Integer.valueOf(count) / Integer.valueOf(size) + 1;
        log.info("up的名字是:{}, 总页数有：{}, 每页有:{}, 总共有:{}", upName, pages, size, count);
        videoList = refreshUPVideoListByPage(upName, upId, size, pages);
        // 获取视频信息之后直接解析音频和视频信息，判断能不能下载
        fillVideoAndAudioInfos(videoList, BiliVideoEnum.Up, BiliFileUtil.getUpSpaceErrorFile(upName), false);
        return videoList;
    }

    /**
     * @Description:
     * @Author: bangbang23333
     * @Date: 2024/8/9 09:53
     */
    public BiliFuckWbiInfo getLoginInfo() {
        Object o = CacheUtil.cacheMap.computeIfAbsent("loginInfo", k -> {
            String realUrl = getLoginInfo;
            String resultInfo = JsoupUtils.getStringDoc(realUrl, header);
            JsonNode myLoginInfo = null;
            try {
                myLoginInfo = objectMapper.readTree(resultInfo);
            } catch (Exception e) {
                log.error("获取登录信息失败", e);
            }
            String img_url = myLoginInfo.get("data").get("wbi_img").get("img_url").asText();
            String sub_url = myLoginInfo.get("data").get("wbi_img").get("sub_url").asText();
            // 输出日志，提示在什么时间触发过一次解析
            log.info("获取登录信息成功，时间:{}", LocalDateTime.now());
            return new BiliFuckWbiInfo(img_url, sub_url);
        });
        return (BiliFuckWbiInfo) o;
    }

    /**
     * @Description:获取up的视频信息
     * @Param: [userId, size, pages]
     * @return: java.util.List<com.example.pojo.BilibiliVideo>
     * @Author: Mr.zengbang
     * @Date: 2022/4/30
     */
    private List<BiliVideoPojo> refreshUPVideoListByPage(String upName, String userId, String size, int pages) {
        List<BiliVideoPojo> videoList = new ArrayList<>();
        for (int i = 1; i < pages + 1; i++) {
            try {
                BiliFuckWbiInfo loginInfo = getLoginInfo();
                String realPageSpaceUrl = String.format(UpPersonDetail, userId, size, i);
                TreeMap<String, Object> paramTreeMap = getTreeMap(realPageSpaceUrl);
                String wbiParamString = BiliWbiUtil.getWbiParamString(paramTreeMap, loginInfo.getImg_key(), loginInfo.getSub_key());
                // 发送请求
                JsonNode doc = JsoupUtils.getJsonNodeDoc(UpPersonDetailWithoutParam + wbiParamString, header);
                for (JsonNode vlist : doc.get("data").get("list").get("vlist")) {
                    BiliVideoPojo bilibiliVideo = objectMapper.readValue(vlist.toString(), BiliVideoPojo.class);
                    bilibiliVideo.setType(BiliVideoEnum.Up.type);
                    videoList.add(bilibiliVideo);
                    // 如果之前已经读取过了该视频信息就不读取了
                    String fileName = BiliFileUtil.getUpVideoFile(bilibiliVideo.getIs_union_video(), upName, bilibiliVideo.getAuthor(), bilibiliVideo.getTitle());
                    BiliFileUtil.isNotExistAndNeedWriteDown(fileName, bilibiliVideo);
                }
            } catch (Exception e) {
                log.error("按页获取Up主视频信息出错, upId:{}, size:{}, page:{}", userId, size, pages, e);
            }
        }
        return videoList;
    }

    /**
     * @Description: 通过avid和bvid拿到视频和音频地址
     * @Author: Mr.zengbang
     * @Date: 2020/12/10
     */
    public void fillVideoAndAudioInfos(List<BiliVideoPojo> bilibiliVideos, BiliVideoEnum type, String errorFileName, boolean shouldDownload) {
        BiliErrorPojo errorList = new BiliErrorPojo();
        String upName = "";
        if (!bilibiliVideos.isEmpty() && type.isUp()) {
            //获取up名字
            upName = BiliUtil.getUpname(bilibiliVideos);
        }
        ArrayList<File> files = new ArrayList<>(FileUtils.listFiles(getAbsoluteFile(VideoConstant.getTargetPath() + FS + upName), FileFilterUtils.fileFileFilter(),
                FileFilterUtils.directoryFileFilter()));
        for (BiliVideoPojo bilibiliVideo : bilibiliVideos) {
            String analysisFileName = "";
            if (type.isUp()) {
                analysisFileName = BiliFileUtil.getUpVideoFile(bilibiliVideo.getIs_union_video(), upName, bilibiliVideo.getAuthor(), bilibiliVideo.getTitle());
            }
            if (type.isFavorite()) {
                // 区分普通视频还是合集视频
                if (StringUtils.isNotEmpty(bilibiliVideo.getCollectionName())) {
                    analysisFileName =
                            BiliFileUtil.getFavoriteVideoFile(bilibiliVideo.getFavoriteName() + FS + bilibiliVideo.getCollectionName(), bilibiliVideo.getTitle());
                } else {
                    analysisFileName = BiliFileUtil.getFavoriteVideoFile(bilibiliVideo.getFavoriteName(),
                            bilibiliVideo.getTitle());
                }
            }
            if (type.isSingle()) {
                analysisFileName = BiliFileUtil.getSingleVideoFile(bilibiliVideo.getCollectionName(), bilibiliVideo.getTitle());
            }
            // 如果不需要下载，且已经知道能不能下，就可以返回了
            if (!shouldDownload) {
                BiliVideoPojo biliVideoPojo = BiliFileUtil.readInfo(getAbsoluteFile(analysisFileName), BiliVideoPojo.class);
                if (null != biliVideoPojo.getCanDownload() && biliVideoPojo.getCanDownload()) {
                    continue;
                }
            }
            // 需要下载还是继续往下走
            fillVideoAndAudio(bilibiliVideo, errorList, analysisFileName, files);
        }
        // 打印错误视频的信息
        if (!errorList.getErrorInfoList().isEmpty()) {
            log.info("爬取up主页失败视频个数:{}, up主名字是:{}", errorList.getErrorInfoList().size(), upName);
            BiliFileUtil.writeInfo(errorFileName, errorList);
        }
    }

    /**
     * @Description:根据avid或者bvid拿取下载链接
     * @Param: [bilibiliVideo]
     * @return: void
     * @Author: Mr.zengbang
     * @Date: 2022/11/3
     */
    public void fillVideoAndAudio(BiliVideoPojo bilibiliVideo, BiliErrorPojo errorList, String analysisFileName, List<File> files) {
        Document doc = null;
        try {
            // 如果文件已经下载了，就不用再解析了
            if (files.stream().anyMatch(file -> file.getName().equals(bilibiliVideo.getVideoName()))) {
                log.info("视频已经下载过了，跳过解析，视频名:{}, 作者是:{}", bilibiliVideo.getTitle(), bilibiliVideo.getAuthor());
                bilibiliVideo.setCanDownload(true);
                BiliFileUtil.writeInfo(analysisFileName, bilibiliVideo);
                return;
            }
            // 如果视频的视频下载时间还有效，则不用再填充了
            if (StringUtils.isNotEmpty(bilibiliVideo.getVideoUrl())) {
                TreeMap<String, Object> treeMap = getTreeMap(bilibiliVideo.getVideoUrl());
                Long deadline = Long.valueOf((String) treeMap.get("deadline"));
                // 有效期在30分钟以内的话
                if (deadline * 1000 > new Date().getTime() + 1800000) {
                    log.info("视频下载信息还在有效期, 无需爬取, 视频的名字是:{}", bilibiliVideo.getTitle());
                    return;
                }
            }
            String realUrlModel = "";
            if (!StringUtils.isEmpty(bilibiliVideo.getBvid())) {
                realUrlModel = String.format(videoAndAudioUrl, bilibiliVideo.getBvid());
            } else if (!StringUtils.isEmpty(bilibiliVideo.getAid())) {
                realUrlModel = String.format(videoAndAudioUrl, bilibiliVideo.getAid());
            }
            if (!StringUtils.isEmpty(bilibiliVideo.getCid())) {
                realUrlModel = String.format(videoAndAudioUrlByPage, bilibiliVideo.getBvid(), bilibiliVideo.getPage());
            }
            doc = JsoupUtils.getDoc(realUrlModel, header);
            //拿到视频和音频的script
            String videoUrl = HtmlUtil.getVideoUrl(doc);
            String audioUrl = HtmlUtil.getAudioUrl(doc);

            if (null != bilibiliVideo.getCanDownload() && !bilibiliVideo.getCanDownload()) {
                log.error("视频没有权限下载，非普通视频, 视频的名字是:{}", bilibiliVideo.getTitle());
            } else if (StringUtils.isEmpty(videoUrl) || StringUtils.isEmpty(audioUrl)) {
                log.error("获取视频下载信息是，发现视频下载信息不全, 视频的名字是:{}", bilibiliVideo.getTitle());
                bilibiliVideo.setCanDownload(false);
            } else {
                bilibiliVideo.setVideoUrl(videoUrl);
                bilibiliVideo.setAudioUrl(audioUrl);
                bilibiliVideo.setCanDownload(true);
                log.info("解析视频和音频信息成功, 标题是:{}, 作者是:{}", bilibiliVideo.getTitle(), bilibiliVideo.getAuthor());
            }
            BiliFileUtil.writeInfo(analysisFileName, bilibiliVideo);
        } catch (Exception e) {
            Map errorInfo = new HashMap();
            errorInfo.put("bilibiliVideo", bilibiliVideo);
            errorInfo.put("document", doc);
            errorList.getErrorInfoList().add(errorInfo);
            log.error("build下载视频和音频信息出错, 视频名字是:{}", bilibiliVideo.getTitle(), e);
        }
    }

    /**
     * @Description:通过avid或者bvid拿到视频的全部p(可存在单次下载和收藏夹下载中)
     * @Param: [avid, bvid]
     * @return: java.util.List<com.example.pojo.BilibiliVideo>
     * @Author: Mr.zengbang
     * @Date: 2020/12/9
     */
    public List<BiliVideoPojo> refreshPageList(String avid, String bvid, String collectionName, String favoriteName, BiliVideoEnum biliVideoEnum) {

        String pageListurl = "https://api.bilibili.com/x/player/pagelist?bvid=" + bvid + "&jsonp=jsonp";
        JsonNode doc = JsoupUtils.getJsonNodeDoc(pageListurl, header);
        List<BiliVideoPojo> videoList = new ArrayList();
        if (doc.get("data") != null) {
            for (JsonNode p : doc.get("data")) {
                BiliVideoPojo bilibiliVideo = new BiliVideoPojo();
                bilibiliVideo.setAid(StringUtils.isEmpty(avid) ? "" : avid);
                bilibiliVideo.setBvid(StringUtils.isEmpty(bvid) ? "" : bvid);
                bilibiliVideo.setTitle(p.get("part").asText());
                bilibiliVideo.setCid(p.get("cid").asText());
                bilibiliVideo.setPage(p.get("page").asText());
                bilibiliVideo.setCollectionName(collectionName);
                bilibiliVideo.setFavoriteName(favoriteName);
                bilibiliVideo.setType(bilibiliVideo.getType());
                videoList.add(bilibiliVideo);
                String filePath = "";
                if (biliVideoEnum.isSingle()) {

                    filePath = BiliFileUtil.getSingleVideoFile(collectionName, bilibiliVideo.getTitle() + ".txt");
                }
                if (biliVideoEnum.isFavorite()) {
                    filePath = BiliFileUtil.getFavoriteVideoFile(favoriteName + FS + collectionName, bilibiliVideo.getTitle() + ".txt");
                }
                BiliFileUtil.isNotExistAndNeedWriteDown(filePath, bilibiliVideo);
            }
        }
        System.out.println("合集视频? 获取到视频数有：" + videoList.size());
        return videoList;
    }

    /**
     * @Description:通过avid和bvid获得cid
     * @Param: [avid, bvid]
     * @return: java.lang.String
     * @Author: Mr.zengbang
     * @Date: 2020/12/9
     */
    public String getCidByAvidOrBvid(String avid, String bvid) {

        String cidUrlModel = "https://api.bilibili.com/x/web-interface/view?%s";
        StringBuilder info = new StringBuilder();
        if (!StringUtils.isEmpty(avid)) {
            info.append("&").append("avid=").append(avid);
        }
        if (!StringUtils.isEmpty(bvid)) {
            info.append("&").append("bvid=").append(bvid);
        }
        String realCidUrlModel = String.format(cidUrlModel, info.toString());
        JsonNode doc = JsoupUtils.getJsonNodeDoc(realCidUrlModel, header);
        String cid = doc.get("data").get("cid").asText();
        return cid;
    }

    /**
     * @Description:下载视频的flv格式
     * @Param: [bilibiliVideo]
     * @return: void
     * @Author: Mr.zengbang
     * @Date: 2020/12/9
     */
 /*   public void downloadVideo(BilibiliVideo bilibiliVideo) throws Exception {

        String downloadVideoModel = "https://api.bilibili
        .com/x/player/playurl?qn=80&%sfnver=0&player=1&otype=json&fnval=0";
        StringBuilder info = new StringBuilder();
        if (!StringUtils.isEmpty(bilibiliVideo.getAvid())) {
            info.append("avid=").append(bilibiliVideo.getAvid()).append("&");
        }
        if (!StringUtils.isEmpty(bilibiliVideo.getBvid())) {
            info.append("bvid=").append(bilibiliVideo.getBvid()).append("&");
        }
        if (!StringUtils.isEmpty(bilibiliVideo.getCid())) {
            info.append("cid=").append(bilibiliVideo.getCid()).append("&");
        } else {
            info.append("cid=").append(getCidByAvidOrBvid(bilibiliVideo.getAvid(), bilibiliVideo.getBvid())).append
            ("&");
        }
        String url = String.format(downloadVideoModel, info.toString());
        JsonNode doc = (JsonNode) JsoupUtils.getDoc(url, JsoupConstant.JSONNODE, header);

        String downloadUrl = doc.get("data").get("durl").get(0).get("url").asText();
        bilibiliVideo.setDownloadUrl(downloadUrl);
        log.info("视频下载地址是:{}", bilibiliVideo);
        //拼接文件名字
        String fileName = bilibiliVideo.getPart() + "-" + bilibiliVideo.getPage() + ".flv";
        String path = VideoConstant.getBasePath() + bilibiliVideo.getTitle() +  File.separator + fileName;
        OpetateFileUtil.checkTarget(path);
        //拼接拿到文件的流
        URLConnection urlConnection = makeUrl(downloadUrl);
        InputStream inputStream = urlConnection.getInputStream();
        //下载
        FileUtils.copyInputStreamToFile(inputStream, new File(path));
        inputStream.close();
    }*/
    public static int convertToSeconds(String time) {
        String[] parts = time.split(":");
        int hours = 0, minutes = 0, seconds = 0;

        if (parts.length == 2) {
            minutes = Integer.parseInt(parts[0]);
            seconds = Integer.parseInt(parts[1]);
        } else if (parts.length == 3) {
            hours = Integer.parseInt(parts[0]);
            minutes = Integer.parseInt(parts[1]);
            seconds = Integer.parseInt(parts[2]);
        }

        return hours * 3600 + minutes * 60 + seconds;
    }

    /**
     * @Description:下载视频的1080P格式
     * @Param: [bilibiliVideo]
     * @return: void
     * @Author: Mr.zengbang
     * @Date: 2020/12/9
     */
    public void downloadHighestVideo(BiliVideoPojo bilibiliVideo, AtomicInteger success, Boolean deleteAfterDownload, String completedVideoPath, String videoPath, String audioPath,
                                     String downLoadErrorFile) {
        // 判断视频是否超长,默认不能超过10分钟
        if (StringUtils.isNotEmpty(bilibiliVideo.getLength()) && convertToSeconds(bilibiliVideo.getLength()) > MAX_LENGTH) {
            log.error("视频超长，不进行下载, 时长是:{}, 视频名称是:{}, 作者是:{}", bilibiliVideo.getLength(), bilibiliVideo.getTitle(),
                    bilibiliVideo.getAuthor());
            success.incrementAndGet();
            return;
        }
        //判断视频是否已经下载了
        if (BiliFileUtil.checkFileExist(completedVideoPath)) {
            log.info("视频已经下载过了, 这次不下载, 视频名称是:{}, 作者是:{}", bilibiliVideo.getTitle(), bilibiliVideo.getAuthor());
            success.incrementAndGet();
            return;
        }
        String videoUrl = bilibiliVideo.getVideoUrl();
        String audioUrl = bilibiliVideo.getAudioUrl();
        // 校验关键参数是否完整
        if (null != bilibiliVideo.getCanDownload() && !bilibiliVideo.getCanDownload()) {
            log.error("准备下载视频时，发现视频没有权限下载, 视频名称是:{}", bilibiliVideo.getTitle());
            success.incrementAndGet();
            return;
        }
        // 校验关键参数是否完整
        if (StringUtils.isEmpty(videoUrl) || StringUtils.isEmpty(audioUrl)) {
            log.error("准备下载视频时，发现视频下载信息不全, 视频名称是:{}", bilibiliVideo.getTitle());
            success.incrementAndGet();
            return;
        }
        try {
            log.info("视频正在下载, 视频名称是:{}, 作者是:{}", bilibiliVideo.getTitle(), bilibiliVideo.getAuthor() == null ? bilibiliVideo.getCollectionName() : bilibiliVideo.getAuthor());
            // 下载视频
            URLConnection videoConnection = buildUrl(videoUrl);
            InputStream videoInputStream = videoConnection.getInputStream();
            OpetateFileUtil.checkTarget(getAbsoluteFile(videoPath).getAbsolutePath());
            FileUtils.copyInputStreamToFile(videoInputStream, getAbsoluteFile(videoPath));
            // 下载音频
            URLConnection audioConnection = buildUrl(audioUrl);
            InputStream audioInputStream = audioConnection.getInputStream();
            FileUtils.copyInputStreamToFile(audioInputStream, getAbsoluteFile(audioPath));
            // 关闭流
            audioInputStream.close();
            videoInputStream.close();
            // 检查文件是否下载完整
            if (!checkFileExist(videoPath)) {
                log.error("视频文件缺失, 文件名称是:{}", new File(VideoConstant.absolutePath + videoPath).getAbsolutePath());
                return;
            }
            if (!checkFileExist(audioPath)) {
                log.error("音频文件缺失, 文件名称是:{}", audioPath);
                return;
            }
            // 合并视频和音频
            List<String> list = VideoCommandUtils.mergeVideoAudio(getAbsoluteFile(videoPath).getAbsolutePath(), getAbsoluteFile(audioPath).getAbsolutePath(),
                    getAbsoluteFile(completedVideoPath).getAbsolutePath(), false);
            CommandResult commandResult = CommandUtils.runCommandSyncReader(list, false);
            if (commandResult.getExitValue() != -1) {
                if (deleteAfterDownload) {
                    Files.deleteIfExists(getAbsoluteFile(videoPath).toPath());
                    Files.deleteIfExists(getAbsoluteFile(audioPath).toPath());
                    log.info("删除视频，名字是：{}", getAbsoluteFile(videoPath).getAbsolutePath());
                    log.info("删除音频，名字是：{}", getAbsoluteFile(audioPath).getAbsolutePath());
                }
            } else {
                log.error("!!!!!!!!!!!文件转码失败, 失败命令是:{}", list);
            }
        } catch (Exception e) {
            log.error("下载视频出错, title:{} ,aid:{} ,bvid:{}", bilibiliVideo.getTitle(), bilibiliVideo.getAid(), bilibiliVideo.getBvid(), e);
            BiliFileUtil.writeInfo(downLoadErrorFile, e.getMessage());
            if (deleteAfterDownload) {
                try {
                    Files.deleteIfExists(getAbsoluteFile(videoPath).toPath());
                    Files.deleteIfExists(getAbsoluteFile(audioPath).toPath());
                    log.info("删除视频，名字是：{}", getAbsoluteFile(videoPath).getAbsolutePath());
                    log.info("删除音频，名字是：{}", getAbsoluteFile(audioPath).getAbsolutePath());
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        } finally {
            success.incrementAndGet();
        }
    }

    /**
     * @Description:包装一下URL，设置一下参数什么的
     * @Param: [downloadUrl]
     * @return: java.net.URLConnection
     * @Author: Mr.zengbang
     * @Date: 2020/12/9
     */
    private URLConnection buildUrl(String downloadUrl) throws IOException {
        URL url = new URL(downloadUrl);
//        log.info("url:{}", url);
        URLConnection urlConnection = url.openConnection();
        urlConnection.setRequestProperty("'accept'", "*/*");
        urlConnection.setRequestProperty("origin", "https://www.bilibili.com");
        urlConnection.setRequestProperty("referer", "https://www.bilibili.com");
        urlConnection.setRequestProperty("accept-encoding", "'gzip, deflate, br'");
        urlConnection.setRequestProperty("accept-language", "zh-CN,zh;q=0.9,en;q=0.8");
        urlConnection.setRequestProperty("sec-fetch-dest", "empty");
        urlConnection.setRequestProperty("sec-fetch-mode", "cors");
        urlConnection.setRequestProperty("sec-fetch-site", "same-site");
        urlConnection.setRequestProperty("range", "bytes=0-");
        urlConnection.setRequestProperty("user-agent", UserAgentConstant.getIndex());
        urlConnection.setConnectTimeout(10 * 1000);
        return urlConnection;
    }

    /**
     * @Description:包装一下URL，设置一下参数什么的
     * @Param: [lastUrl]
     * @return: java.net.URLConnection
     * @Author: Mr.zengbang
     * @Date: 2020/12/9
     */
    private URLConnection makeUrl(String lastUrl) throws IOException {

        URL url = new URL(lastUrl);
        URLConnection urlConnection = url.openConnection();
        urlConnection.setRequestProperty("referer", "https://www.bilibili.com");
        urlConnection.setRequestProperty("sec-fetch-mode", "no-cors");
        urlConnection.setRequestProperty("user-agent", UserAgentConstant.getIndex());
        urlConnection.setConnectTimeout(10 * 1000);
        return urlConnection;
    }

    public static String createConnection(String url) {
        String jsonText = null;
        Connection connection = Jsoup.connect(url).timeout(3000).ignoreContentType(true);
        Map<String, String> heads = new HashMap<>();
        heads.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8," + "application/signed-exchange;v=b3");
        heads.put("Accept-Encoding", "gzip, deflate, br");
        heads.put("Accept-Language", "en,zh-CN;q=0.9,zh;q=0.8");
        heads.put("Cache-Control", "max-age=0");
        heads.put("Connection", "keep-alive");
        heads.put("Cookie", "填写自己的cookie");
        heads.put("Host", "api.bilibili.com");
        heads.put("Sec-Fetch-Mode", "navigate");
        heads.put("Sec-Fetch-Site", "none");
        heads.put("Sec-Fetch-User", "?1");
        heads.put("Upgrade-Insecure-Requests", "1");
        heads.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/77.0" + ".3865.90 Safari/537.36");
        connection.headers(heads);
        try {
            jsonText = connection.get().text();
        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("建立获取cid连接失败");
        }
        return jsonText;
    }
}
